using Gj;
using Unity.Burst;
using Unity.Burst.Intrinsics;
using Unity.Collections;
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;

[UpdateAfter(typeof(AiSystem))]
public partial class MoveSystem : SystemBase
{
    private BeginInitializationEntityCommandBufferSystem CommandBufferSystem;
    private EntityQuery moveAmendGroup;
    private EntityQuery moveGroup;
    private EntityQuery moveHoldUpdateGroup;
    private EntityQuery moveSpeedControlGroup;
    private EntityQuery moveVehicleGroup;
    private EntityQuery rotateGroup;

    protected override void OnCreate()
    {
        CommandBufferSystem = World.GetOrCreateSystemManaged<BeginInitializationEntityCommandBufferSystem>();
        // 可移动对象
        moveGroup = GetEntityQuery(new EntityQueryDesc
        {
            All = new[]
            {
                ComponentType.ReadOnly<MoveData>(),
                ComponentType.ReadOnly<MoveStatusData>(),
                ComponentType.ReadOnly<LocalTransform>()
            },
            None = new[]
            {
                ComponentType.ReadOnly<ActionFrozenData>(),
                ComponentType.ReadOnly<ActionHoldData>()
            }
        });

        moveSpeedControlGroup = GetEntityQuery(new EntityQueryDesc
        {
            All = new[]
            {
                ComponentType.ReadOnly<MoveData>(),
                ComponentType.ReadOnly<MoveSpeedControlData>()
            }
        });

        moveAmendGroup = GetEntityQuery(new EntityQueryDesc
        {
            All = new[]
            {
                ComponentType.ReadOnly<MoveData>(),
                ComponentType.ReadOnly<MoveStatusData>(),
                ComponentType.ReadOnly<MoveAmendData>(),
                ComponentType.ReadOnly<LocalTransform>()
            }
            // None = new[]
            // {
            //     ComponentType.ReadOnly<ActionFrozenData>(),
            // }
        });

        moveVehicleGroup = GetEntityQuery(new EntityQueryDesc
        {
            All = new[]
            {
                ComponentType.ReadOnly<MoveData>(),
                ComponentType.ReadOnly<MoveStatusData>(),
                ComponentType.ReadOnly<MoveVehicleData>(),
                ComponentType.ReadOnly<LocalTransform>()
            }
        });
        moveHoldUpdateGroup = GetEntityQuery(new EntityQueryDesc
        {
            All = new[]
            {
                ComponentType.ReadOnly<MoveHoldData>(),
                ComponentType.ReadOnly<LocalTransform>()
            },
            None = new[]
            {
                ComponentType.ReadWrite<CastColliderData>(),
                ComponentType.ReadWrite<CastHitsData>()
            }
        });
        rotateGroup = GetEntityQuery(new EntityQueryDesc
        {
            All = new[]
            {
                ComponentType.ReadOnly<LookData>(),
                ComponentType.ReadOnly<LookStatusData>(),
                ComponentType.ReadOnly<LocalTransform>()
            },
            None = new[]
            {
                ComponentType.ReadOnly<ActionFrozenData>(),
                ComponentType.ReadOnly<ActionHoldData>()
            }
        });
    }

    protected override void OnUpdate()
    {
        var inputDeps = Dependency;
        var deltaTime = UnityEngine.Time.deltaTime;
        var elapsedTime = Global.gameTime;
        var commandBuffer = CommandBufferSystem.CreateCommandBuffer().AsParallelWriter();
        var EntityType = GetEntityTypeHandle();
        var MoveDataType = GetComponentTypeHandle<MoveData>();
        var MoveStatusDataType = GetComponentTypeHandle<MoveStatusData>();
        // var MoveSpeedControlDataType = GetComponentTypeHandle<MoveSpeedControlData>();
        // var MoveVehicleDataType = GetComponentTypeHandle<MoveVehicleData>();
        // var MoveAmendDataType = GetComponentTypeHandle<MoveAmendData>();
        // var MoveHoldDataType = GetComponentTypeHandle<MoveHoldData>();
        var TranslationType = GetComponentTypeHandle<LocalTransform>();
        var TranslationFromEntity = GetComponentLookup<LocalTransform>();
        var EntityDataFromEntity = GetComponentLookup<EntityData>();

        // var moveAmendJob = new MoveAmendJob
        // {
        //     MoveAmendDataType = MoveAmendDataType,
        //     TranslationType = TranslationType,
        //     deltaTime = deltaTime
        // };
        // inputDeps = moveAmendJob.Schedule(moveAmendGroup, inputDeps);

        var movePreJob = new MovePreJob
        {
            CommandBuffer = commandBuffer,
            MoveDataType = MoveDataType,
            MoveStatusDataType = MoveStatusDataType,
            EntityType = EntityType,
            TranslationFromEntity = TranslationFromEntity,
            EntityDataFromEntity = EntityDataFromEntity
        };
        inputDeps = movePreJob.Schedule(moveGroup, inputDeps);

        // var moveSpeedControlJob = new MoveSpeedControlJob
        // {
        //     CommandBuffer = commandBuffer,
        //     MoveDataType = MoveDataType,
        //     MoveSpeedControlDataType = MoveSpeedControlDataType,
        //     EntityType = EntityType,
        //     deltaTime = deltaTime
        // };
        // inputDeps = moveSpeedControlJob.Schedule(moveSpeedControlGroup, inputDeps);

        var moveJob = new MoveJob
        {
            MoveDataType = MoveDataType,
            MoveStatusDataType = MoveStatusDataType,
            TranslationType = TranslationType,
            deltaTime = deltaTime,
            elapsedTime = elapsedTime,
        };
        inputDeps = moveJob.Schedule(moveGroup, inputDeps);

        // var moveVehiclePreJob = new MoveVehiclePreJob
        // {
        //     MoveVehicleDataType = MoveVehicleDataType,
        //     TranslationFromEntity = TranslationFromEntity
        // };
        // inputDeps = moveVehiclePreJob.Schedule(moveVehicleGroup, inputDeps);

        // var moveVehicleJob = new MoveVehicleJob
        // {
        //     MoveVehicleDataType = MoveVehicleDataType,
        //     TranslationType = TranslationType
        // };
        // inputDeps = moveVehicleJob.Schedule(moveVehicleGroup, inputDeps);
        //
        // var moveHoldUpdateJob = new MoveHoldUpdateJob
        // {
        //     CommandBuffer = commandBuffer,
        //     MoveHoldDataType = MoveHoldDataType,
        //     TranslationType = TranslationType,
        //     EntityType = EntityType
        // };
        // inputDeps = moveHoldUpdateJob.Schedule(moveHoldUpdateGroup, inputDeps);

        var LookDataType = GetComponentTypeHandle<LookData>();
        var LookStatusDataType = GetComponentTypeHandle<LookStatusData>();

        var rotatePreJob = new LookPreJob
        {
            CommandBuffer = commandBuffer,
            LookDataType = LookDataType,
            LookStatusDataType = LookStatusDataType,
            TranslationType = TranslationType,
            EntityType = EntityType,
            TranslationFromEntity = TranslationFromEntity
        };
        inputDeps = rotatePreJob.Schedule(rotateGroup, inputDeps);

        var rotateJob = new LookJob
        {
            LookDataType = LookDataType,
            LookStatusDataType = LookStatusDataType,
            RotationType = TranslationType,
            deltaTime = deltaTime,
            elapsedTime = elapsedTime,
        };
        inputDeps = rotateJob.Schedule(rotateGroup, inputDeps);

        CommandBufferSystem.AddJobHandleForProducer(inputDeps);
        inputDeps.Complete();
    }

    [BurstCompile]
    private struct MoveAmendJob : IJobChunk
    {
        public ComponentTypeHandle<MoveAmendData> MoveAmendDataType;

        public ComponentTypeHandle<LocalTransform> TranslationType;

        [ReadOnly] public float deltaTime;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var moveAmendDataArray = chunk.GetNativeArray(ref MoveAmendDataType);
            var translationArray = chunk.GetNativeArray(ref TranslationType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var moveAmendData = moveAmendDataArray[i];
                var translation = translationArray[i];

                if (moveAmendData._surplusFrameTime > 0)
                {
                    translation.Position += moveAmendData._offsetPosition * (deltaTime / moveAmendData._totalFrameTime);
                    translationArray[i] = translation;
                    moveAmendData._surplusFrameTime -= deltaTime;
                    moveAmendDataArray[i] = moveAmendData;
                }
            }
        }
    }

    [BurstCompile]
    private struct MovePreJob : IJobChunk
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;

        public ComponentTypeHandle<MoveData> MoveDataType;

        [ReadOnly] public ComponentTypeHandle<MoveStatusData> MoveStatusDataType;

        [ReadOnly] public EntityTypeHandle EntityType;
        [ReadOnly] public ComponentLookup<LocalTransform> TranslationFromEntity;

        [ReadOnly] public ComponentLookup<EntityData> EntityDataFromEntity;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var moveDataArray = chunk.GetNativeArray(ref MoveDataType);
            var moveStatusDataArray = chunk.GetNativeArray(ref MoveStatusDataType);
            var entities = chunk.GetNativeArray(EntityType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var moveData = moveDataArray[i];
                var moveStatus = moveStatusDataArray[i];

                switch (moveStatus.status)
                {
                    case MoveStatus.Target:
                        var entity = entities[i];
                        var entityData = EntityDataFromEntity[entity];
                        var translation = TranslationFromEntity[entity];
                        if (moveData.target != Entity.Null && EntityDataFromEntity.HasComponent(moveData.target))
                        {
                            var targetEntityData = EntityDataFromEntity[moveData.target];
                            var position = TranslationFromEntity[moveData.target];
                            var gap = Vector3.Normalize(position.Position - translation.Position) *
                                      (targetEntityData.radius + entityData.radius);
                            moveData.position = (Vector3)position.Position - gap;
                            moveDataArray[i] = moveData;
                        }
                        else
                        {
                            CommandBuffer.AddComponent(unfilteredChunkIndex, entity,
                                new MoveStatusData { status = MoveStatus.None });
                        }

                        break;
                }
            }
        }
    }

    [BurstCompile]
    private struct MoveSpeedControlJob : IJobChunk
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;
        [ReadOnly] public EntityTypeHandle EntityType;

        public ComponentTypeHandle<MoveSpeedControlData> MoveSpeedControlDataType;

        public ComponentTypeHandle<MoveData> MoveDataType;

        [ReadOnly] public float deltaTime;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var moveSpeedControlDataArray = chunk.GetNativeArray(ref MoveSpeedControlDataType);
            var moveDataArray = chunk.GetNativeArray(ref MoveDataType);
            var entities = chunk.GetNativeArray(EntityType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var moveData = moveDataArray[i];
                var moveSpeedControl = moveSpeedControlDataArray[i];
                var entity = entities[i];

                moveSpeedControl._time += deltaTime;

                var t = moveSpeedControl._time / moveSpeedControl.time;
                if (t > 1) t = 1;

                moveData.speed = MathTools.Lerp(moveSpeedControl.origin, moveSpeedControl.target,
                    t);

                moveDataArray[i] = moveData;
                moveSpeedControlDataArray[i] = moveSpeedControl;

                if (t == 1) CommandBuffer.RemoveComponent<MoveSpeedControlData>(unfilteredChunkIndex, entity);
            }
        }
    }

    [BurstCompile]
    private struct MoveJob : IJobChunk
    {
        public ComponentTypeHandle<MoveData> MoveDataType;

        [ReadOnly] public ComponentTypeHandle<MoveStatusData> MoveStatusDataType;

        public ComponentTypeHandle<LocalTransform> TranslationType;

        [ReadOnly] public float deltaTime;
        [ReadOnly] public double elapsedTime;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var moveDataArray = chunk.GetNativeArray(ref MoveDataType);
            var moveStatusDataArray = chunk.GetNativeArray(ref MoveStatusDataType);
            var translationArray = chunk.GetNativeArray(ref TranslationType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var moveData = moveDataArray[i];
                var moveStatus = moveStatusDataArray[i];
                var translation = translationArray[i];

                switch (moveStatus.status)
                {
                    case MoveStatus.None:
                    case MoveStatus.Stop:
                        continue;
                    case MoveStatus.Line:
                        if (moveData.endTime > 0)
                        {
                            moveData.progress = (float)(elapsedTime - moveData.startTime - moveData.waitTime) / (moveData.endTime - moveData.startTime - moveData.waitTime);
                        }
                        else
                        {
                            moveData.progress += deltaTime * moveData.speed / moveData.distance;
                        }

                        if (moveData.progress >= 1)
                        {
                            moveData.progress = 1;
                            translation.Position = moveData.end;
                        }
                        else
                        {
                            translation.Position = MathTools.Lerp(moveData.start, moveData.end,
                                moveData.progress);
                        }
                        break;
                    case MoveStatus.Turn:
                        if (moveData.startTime > 0)
                        {
                            moveData.progress = (float)(elapsedTime - moveData.startTime - moveData.waitTime) / (moveData.endTime - moveData.startTime - moveData.waitTime);
                        }
                        else
                        {
                            moveData.progress += deltaTime * moveData.speed / moveData.distance;
                        }

                        if (moveData.progress >= 1)
                        {
                            moveData.progress = 1;
                            translation.Position = moveData.end;
                        }
                        else
                        {
                            translation.Position = MathTools.SBLP(moveData.start, moveData.middle, moveData.end,
                                moveData.progress);
                        }
                        break;
                    case MoveStatus.Teleport:
                        translation.Position = moveData.position;
                        break;
                    case MoveStatus.Target:
                    case MoveStatus.Position:
                        translation.Position = Vector3.MoveTowards(translation.Position, moveData.position,
                            deltaTime * moveData.speed);
                        break;
                    case MoveStatus.Direction:
                        var limit = Vector3.Distance(translation.Position, moveData.limitPosition);
                        var target = translation.Position + deltaTime * moveData.speed * moveData.direction;
                        if (limit <= Vector3.Distance(translation.Position, target))
                            translation.Position =
                                moveData.limitPosition; //translation.Value + (limit - 0.1f) * moveData.direction;
                        else
                            translation.Position = target;
                        break;
                }

                moveDataArray[i] = moveData;
                translationArray[i] = translation;
            }
        }
    }

    [BurstCompile]
    private struct MoveVehiclePreJob : IJobChunk
    {
        public ComponentTypeHandle<MoveVehicleData> MoveVehicleDataType;
        [ReadOnly] public ComponentLookup<LocalTransform> TranslationFromEntity;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var moveVehicleDataArray = chunk.GetNativeArray(ref MoveVehicleDataType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var moveVehicleData = moveVehicleDataArray[i];

                var targetTranslation = TranslationFromEntity[moveVehicleData.target];

                moveVehicleData.prev = moveVehicleData.last;
                moveVehicleData.last = targetTranslation.Position;
                moveVehicleDataArray[i] = moveVehicleData;
            }
        }
    }

    [BurstCompile]
    private struct MoveVehicleJob : IJobChunk
    {
        [ReadOnly] public ComponentTypeHandle<MoveVehicleData> MoveVehicleDataType;

        public ComponentTypeHandle<LocalTransform> TranslationType;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var moveVehicleDataArray = chunk.GetNativeArray(ref MoveVehicleDataType);
            var translationArray = chunk.GetNativeArray(ref TranslationType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var moveVehicleData = moveVehicleDataArray[i];
                var translation = translationArray[i];

                var diff = moveVehicleData.last - moveVehicleData.prev;

                var limit = Vector3.Distance(moveVehicleData.last, translation.Position);
                if (limit > moveVehicleData.radius) continue;
                translation.Position += diff;
                translationArray[i] = translation;
            }
        }
    }

    [BurstCompile]
    private struct MoveHoldUpdateJob : IJobChunk
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;
        [ReadOnly] public ComponentTypeHandle<MoveHoldData> MoveHoldDataType;
        [ReadOnly] public ComponentTypeHandle<LocalTransform> TranslationType;
        [ReadOnly] public EntityTypeHandle EntityType;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var moveHoldDataArray = chunk.GetNativeArray(ref MoveHoldDataType);
            var translationArray = chunk.GetNativeArray(ref TranslationType);
            var entities = chunk.GetNativeArray(EntityType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var moveHoldData = moveHoldDataArray[i];
                var translation = translationArray[i];
                var entity = entities[i];
                CommandBuffer.AddComponent(unfilteredChunkIndex, entity, new CastColliderData
                {
                    type = moveHoldData.type,
                    Start = translation.Position,
                    End = translation.Position,
                    radius = moveHoldData.radius
                });
            }
        }
    }

    [BurstCompile]
    private struct LookPreJob : IJobChunk
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;

        public ComponentTypeHandle<LookData> LookDataType;
        [ReadOnly] public ComponentTypeHandle<LookStatusData> LookStatusDataType;
        [ReadOnly] public EntityTypeHandle EntityType;
        [ReadOnly] public ComponentLookup<LocalTransform> TranslationFromEntity;
        [ReadOnly] public ComponentTypeHandle<LocalTransform> TranslationType;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var lookDataArray = chunk.GetNativeArray(ref LookDataType);
            var lookStatusDataArray = chunk.GetNativeArray(ref LookStatusDataType);
            var translationArray = chunk.GetNativeArray(ref TranslationType);
            var entities = chunk.GetNativeArray(EntityType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var lookData = lookDataArray[i];
                var lookStatus = lookStatusDataArray[i];
                var translation = translationArray[i];

                switch (lookStatus.status)
                {
                    case LookStatus.Target:
                        if (lookData.target != Entity.Null && TranslationFromEntity.HasComponent(lookData.target))
                        {
                            lookData.position = TranslationFromEntity[lookData.target].Position;
                            lookDataArray[i] = lookData;
                        }
                        else
                        {
                            CommandBuffer.AddComponent(unfilteredChunkIndex, entities[i],
                                new LookStatusData { status = LookStatus.None });
                        }

                        break;
                    case LookStatus.ForWard:
                        if (!MathTools.Equal(lookData.current, translation.Position))
                        {
                            lookData.last = lookData.current;
                            lookData.current = translation.Position;
                            lookDataArray[i] = lookData;
                        }

                        break;
                    case LookStatus.Sync:
                        if (lookData.target != Entity.Null && TranslationFromEntity.HasComponent(lookData.target))
                        {
                            lookData.rotation = TranslationFromEntity[lookData.target].Rotation;
                            lookDataArray[i] = lookData;
                        }
                        else
                        {
                            CommandBuffer.AddComponent(unfilteredChunkIndex, entities[i],
                                new LookStatusData { status = LookStatus.None });
                        }

                        break;
                }
            }
        }
    }

    [BurstCompile]
    private struct LookJob : IJobChunk
    {
        [ReadOnly] public ComponentTypeHandle<LookData> LookDataType;
        [ReadOnly] public ComponentTypeHandle<LookStatusData> LookStatusDataType;
        public ComponentTypeHandle<LocalTransform> RotationType;

        [ReadOnly] public float deltaTime;
        [ReadOnly] public double elapsedTime;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var lookDataArray = chunk.GetNativeArray(ref LookDataType);
            var lookStatusDataArray = chunk.GetNativeArray(ref LookStatusDataType);
            var rotationArray = chunk.GetNativeArray(ref RotationType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var lookData = lookDataArray[i];
                var lookStatus = lookStatusDataArray[i];
                var rotate = rotationArray[i];

                switch (lookStatus.status)
                {
                    case LookStatus.None:
                        break;
                    case LookStatus.Target:
                    case LookStatus.Position:
                        if (Vector3.Distance(rotate.Position, lookData.position) > 0)
                        {
                            rotate.Rotation = Quaternion.RotateTowards(rotate.Rotation,
                                Quaternion.LookRotation(lookData.position - rotate.Position),
                                deltaTime * lookData.speed * Mathf.Rad2Deg);
                            rotationArray[i] = rotate;
                        }

                        break;
                    case LookStatus.Direction:
                        rotate.Rotation = Quaternion.RotateTowards(rotate.Rotation,
                            Quaternion.LookRotation(lookData.direction, Vector3.up),
                            deltaTime * lookData.speed * Mathf.Rad2Deg);
                        rotationArray[i] = rotate;
                        break;
                    case LookStatus.Angle:
                        rotate.Rotation = Quaternion.RotateTowards(rotate.Rotation,
                            Quaternion.Euler(0, lookData.angle, 0), deltaTime * lookData.speed * Mathf.Rad2Deg);
                        rotationArray[i] = rotate;
                        break;
                    case LookStatus.Rotate:
                        rotate.Rotation = Quaternion.RotateTowards(rotate.Rotation,
                            Quaternion.LookRotation(lookData.direction, Vector3.up) * rotate.Rotation,
                            deltaTime * lookData.speed * Mathf.Rad2Deg);
                        rotationArray[i] = rotate;
                        break;
                    case LookStatus.ForWard:
                        rotate.Rotation = Quaternion.RotateTowards(rotate.Rotation,
                            Quaternion.LookRotation(rotate.Position - lookData.last),
                            deltaTime * lookData.speed * Mathf.Rad2Deg);
                        rotationArray[i] = rotate;
                        break;
                    case LookStatus.Sync:
                        rotate.Rotation = Quaternion.RotateTowards(rotate.Rotation, lookData.rotation,
                            deltaTime * lookData.speed * Mathf.Rad2Deg);
                        rotationArray[i] = rotate;
                        break;
                }
            }
        }
    }
}